/*
 * 11/19/04 1.0 moved to LGPL.
 * 02/23/99 JavaConversion by E.B
 * Don Cross, April 1993.
 * RIFF file format classes.
 * See Chapter 8 of "Multimedia Programmer's Reference" in
 * the Microsoft Windows SDK.
 *  
 *-----------------------------------------------------------------------
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *----------------------------------------------------------------------
 */

package javazoom.jl.converter;

import java.io.IOException;
import java.io.RandomAccessFile;


/**
 * Class to manage RIFF files
 */
public class RiffFile
{
   class RiffChunkHeader
   {
     	public int    ckID = 0;       // Four-character chunk ID
    	public int    ckSize = 0;     // Length of data in chunk
    	public RiffChunkHeader()
    	{}
   }
 
 
   // DDCRET
   public static final int	DDC_SUCCESS = 0;           // The operation succeded
   public static final int	DDC_FAILURE = 1;           // The operation failed for unspecified reasons
   public static final int	DDC_OUT_OF_MEMORY = 2;     // Operation failed due to running out of memory
   public static final int	DDC_FILE_ERROR = 3;        // Operation encountered file I/O error
   public static final int	DDC_INVALID_CALL = 4;      // Operation was called with invalid parameters
   public static final int	DDC_USER_ABORT = 5;        // Operation was aborted by the user
   public static final int	DDC_INVALID_FILE = 6;      // File format does not match
   
   // RiffFileMode
   public static final int	RFM_UNKNOWN = 0;      	   // undefined type (can use to mean "N/A" or "not open")
   public static final int	RFM_WRITE = 1;        	   // open for write
   public static final int	RFM_READ = 2;         	   // open for read

   private RiffChunkHeader   	riff_header;      // header for whole file
   protected int      			fmode;            // current file I/O mode
   protected RandomAccessFile 	file;             // I/O stream to use

   /**
    * Dummy Constructor
	*/
   public RiffFile()
   {
      file = null;
      fmode = RFM_UNKNOWN;
      riff_header = new RiffChunkHeader();
   
      riff_header.ckID = FourCC("RIFF");
   	  riff_header.ckSize = 0;
   }
 
   /**
    * Return File Mode.
	*/
   public int CurrentFileMode()
   {return fmode;}
   
   /**
    * Open a RIFF file.
	*/
   public int Open(String Filename, int NewMode)
   {
      int retcode = DDC_SUCCESS;
   
      if ( fmode != RFM_UNKNOWN )
      {
   	  	retcode = Close();
      }
   
      if ( retcode == DDC_SUCCESS )
      {
   	  	switch ( NewMode )
   	  	{
   		 case RFM_WRITE:
			try
			{	
				file = new RandomAccessFile(Filename,"rw");
				
			    try
				{
   				   // Write the RIFF header...
   				   // We will have to come back later and patch it!
				   byte[] br = new byte[8];
				   br[0] = (byte) ((riff_header.ckID >>> 24) & 0x000000FF);
				   br[1] = (byte) ((riff_header.ckID >>> 16) & 0x000000FF);
				   br[2] = (byte) ((riff_header.ckID >>> 8) & 0x000000FF);
				   br[3] = (byte) (riff_header.ckID & 0x000000FF);
				  
			       byte br4 = (byte) ((riff_header.ckSize >>> 24)& 0x000000FF);
				   byte br5 = (byte) ((riff_header.ckSize >>> 16)& 0x000000FF);
				   byte br6 = (byte) ((riff_header.ckSize >>> 8)& 0x000000FF);
				   byte br7 = (byte) (riff_header.ckSize & 0x000000FF);
				   
				   br[4] = br7;
				   br[5] = br6;
				   br[6] = br5;
				   br[7] = br4;
				   
					file.write(br,0,8);
				   fmode = RFM_WRITE;
				} catch (IOException ioe)
				  {
				    file.close();
				  	fmode = RFM_UNKNOWN;
		 		  }
			} catch (IOException ioe)
			  {
			     fmode = RFM_UNKNOWN;
   				 retcode = DDC_FILE_ERROR;
			  }
   			  break;
   
   		 case RFM_READ:
			try
			{	
				file = new RandomAccessFile(Filename,"r");
			    try
				{
   				   // Try to read the RIFF header...   				   
				   byte[] br = new byte[8];
				   file.read(br,0,8);
				   fmode = RFM_READ;
				   riff_header.ckID = ((br[0]<<24)& 0xFF000000) | ((br[1]<<16)&0x00FF0000) | ((br[2]<<8)&0x0000FF00) | (br[3]&0x000000FF);
				   riff_header.ckSize = ((br[4]<<24)& 0xFF000000) | ((br[5]<<16)&0x00FF0000) | ((br[6]<<8)&0x0000FF00) | (br[7]&0x000000FF);
				} catch (IOException ioe)
				  {
				    file.close();
				  	fmode = RFM_UNKNOWN;
		 		  }
			} catch (IOException ioe)
			  {
			 	 fmode = RFM_UNKNOWN;
   				 retcode = DDC_FILE_ERROR;
			  }
   			  break;
   		 default:
   			  retcode = DDC_INVALID_CALL;
   	  	}
      }
   	 return retcode;
   }
   
   /**
    * Write NumBytes data.
	*/
   public int Write(byte[] Data, int NumBytes )
   {
      if ( fmode != RFM_WRITE )
      {
   	  	return DDC_INVALID_CALL;
      }
	  try
  	  {   	
		file.write(Data,0,NumBytes);
	    fmode = RFM_WRITE;
 	  } 
	  catch (IOException ioe)
	  {
			return DDC_FILE_ERROR;
	  }
      riff_header.ckSize += NumBytes;
   	  return DDC_SUCCESS;
   }
   


   /**
    * Write NumBytes data.
	*/     
   public int Write(short[] Data, int NumBytes )
   {
      byte[] theData = new byte[NumBytes];
      int yc = 0;
      for (int y = 0;y<NumBytes;y=y+2)
      {
        theData[y] = (byte) (Data[yc] & 0x00FF);
        theData[y+1] =(byte) ((Data[yc++] >>> 8) & 0x00FF);
      }
      if ( fmode != RFM_WRITE )
      {
   	  	return DDC_INVALID_CALL;
      }
	  try
  	  {   	
		file.write(theData,0,NumBytes);
	    fmode = RFM_WRITE;
 	  } 
	  catch (IOException ioe)
	   {
	  	return DDC_FILE_ERROR;
	   }
	  riff_header.ckSize += NumBytes;
   	  return DDC_SUCCESS;
   }

   /**
    * Write NumBytes data.
	*/
   public int Write(RiffChunkHeader Triff_header, int NumBytes )
   {
	  byte[] br = new byte[8];
	  br[0] = (byte) ((Triff_header.ckID >>> 24) & 0x000000FF);
	  br[1] = (byte) ((Triff_header.ckID >>> 16) & 0x000000FF);
	  br[2] = (byte) ((Triff_header.ckID >>> 8) & 0x000000FF);
	  br[3] = (byte) (Triff_header.ckID & 0x000000FF);

	  byte br4 = (byte) ((Triff_header.ckSize >>> 24)& 0x000000FF);
	  byte br5 = (byte) ((Triff_header.ckSize >>> 16)& 0x000000FF);
	  byte br6 = (byte) ((Triff_header.ckSize >>> 8)& 0x000000FF);
	  byte br7 = (byte) (Triff_header.ckSize & 0x000000FF);
	  
	  br[4] = br7;
	  br[5] = br6;
	  br[6] = br5;
	  br[7] = br4;	  
	  
      if ( fmode != RFM_WRITE )
      {
   	  	return DDC_INVALID_CALL;
      }
	  try
  	  {   	
		file.write(br,0,NumBytes);
	    fmode = RFM_WRITE;
 	  } catch (IOException ioe)
		 {
			return DDC_FILE_ERROR;
		 }
      riff_header.ckSize += NumBytes;
   	  return DDC_SUCCESS;
   }

   /**
    * Write NumBytes data.
	*/
   public int Write(short Data, int NumBytes )
   {
      short theData = (short) ( ((Data>>>8)&0x00FF) | ((Data<<8)&0xFF00) );
      if ( fmode != RFM_WRITE )
      {
   	  	return DDC_INVALID_CALL;
      }
	  try
  	  {   	
		file.writeShort(theData);
	    fmode = RFM_WRITE;
 	  } catch (IOException ioe)
		 {
			return DDC_FILE_ERROR;
		 }
      riff_header.ckSize += NumBytes;
   	  return DDC_SUCCESS;
   }
   /**
    * Write NumBytes data.
	*/
   public int Write(int Data, int NumBytes )
   {
    short theDataL = (short) ((Data>>>16)&0x0000FFFF);
    short theDataR = (short) (Data&0x0000FFFF);
    short theDataLI = (short) ( ((theDataL>>>8)&0x00FF) | ((theDataL<<8)&0xFF00) );
    short theDataRI = (short) ( ((theDataR>>>8)&0x00FF) | ((theDataR<<8)&0xFF00) );
    int theData = ((theDataRI<<16)&0xFFFF0000) | (theDataLI&0x0000FFFF);
      if ( fmode != RFM_WRITE )
      {
   	  	return DDC_INVALID_CALL;
      }
	  try
  	  {   	
		file.writeInt(theData);
	    fmode = RFM_WRITE;
 	  } catch (IOException ioe)
		 {
			return DDC_FILE_ERROR;
		 }
      riff_header.ckSize += NumBytes;
   	  return DDC_SUCCESS;
   }



   /**
    * Read NumBytes data.
	*/   
   public int Read (byte[] Data, int NumBytes)
   {
      int retcode = DDC_SUCCESS;
  	  try
	  {
   		  file.read(Data,0,NumBytes);
      } catch (IOException ioe)
		    {
			   retcode = DDC_FILE_ERROR;
			}   
      return retcode;
   }
   
   /**
    * Expect NumBytes data.
	*/
   public int Expect(String Data, int NumBytes )
   {
      byte	target = 0;
	  int	cnt = 0;
   	  try
 	  {
	  	   while ((NumBytes--) != 0)
      	   {
    	 		target = file.readByte();
				if (target != Data.charAt(cnt++)) return DDC_FILE_ERROR;
		   }			
      } catch (IOException ioe)
 		 {
 		   return DDC_FILE_ERROR;
		 }   
   		return DDC_SUCCESS;
   }
   
   /**
    * Close Riff File.
	* Length is written too.
	*/
   public int Close()
   {
     int retcode = DDC_SUCCESS;
   
      switch ( fmode )
      {
   	  	case RFM_WRITE:
	  	  try
		  {
			file.seek(0);
	  	    try
			{
				byte[] br = new byte[8];
				br[0] = (byte) ((riff_header.ckID >>> 24) & 0x000000FF);
				br[1] = (byte) ((riff_header.ckID >>> 16) & 0x000000FF);
				br[2] = (byte) ((riff_header.ckID >>> 8) & 0x000000FF);
				br[3] = (byte) (riff_header.ckID & 0x000000FF);
			    
				br[7] = (byte) ((riff_header.ckSize >>> 24)& 0x000000FF);
				br[6] = (byte) ((riff_header.ckSize >>> 16)& 0x000000FF);
				br[5] = (byte) ((riff_header.ckSize >>> 8)& 0x000000FF);
				br[4] = (byte) (riff_header.ckSize & 0x000000FF);
				file.write(br,0,8);	
				file.close();
			} catch (IOException ioe)
			    {
				   retcode = DDC_FILE_ERROR;
				}
		  } catch (IOException ioe)
		    {
			   retcode = DDC_FILE_ERROR;
			}
   		   break;
   
   	  	case RFM_READ:
   	  	   try
		   {
   		        file.close();
   		   } catch (IOException ioe)
		    {
			   retcode = DDC_FILE_ERROR;
			}
   		   break;
      }
      file = null;   
      fmode = RFM_UNKNOWN;
   	  return retcode;
   }

   /**
    * Return File Position.
	*/
   public long CurrentFilePosition()
   {
   	  long	position;
 	  try
	  {
   	      position = file.getFilePointer();
   	  } catch (IOException ioe)
	    {
	      position = -1;
	    }
      return position;
   }

   /**
    * Write Data to specified offset.
	*/
   public int Backpatch (long FileOffset, RiffChunkHeader Data, int NumBytes )
   {
      if (file == null)
      {
   	  	return DDC_INVALID_CALL;
      }
 	  try
	  {
   	      file.seek(FileOffset);
   	  } catch (IOException ioe)
	    {
	      return DDC_FILE_ERROR;
	    }   
   	  return Write ( Data, NumBytes );
   }

   public int Backpatch (long FileOffset, byte[] Data, int NumBytes )
   {
      if (file == null)
      {
   	  	return DDC_INVALID_CALL;
      }
 	  try
	  {
   	      file.seek(FileOffset);
   	  } catch (IOException ioe)
	    {
	      return DDC_FILE_ERROR;
	    }   
   	  return Write ( Data, NumBytes );
   }


   /**
    * Seek in the File.
	*/
   protected int Seek(long offset)
   {
      int rc;
 	  try
	  {
   	      file.seek(offset);
		  rc = DDC_SUCCESS;
   	  } catch (IOException ioe)
	    {
	      rc = DDC_FILE_ERROR;
	    }
   	  return rc;
   }

   /**
    * Error Messages.
	*/
   private String DDCRET_String(int retcode)
   {
   	 switch ( retcode )
   	 {
	  case DDC_SUCCESS:          return "DDC_SUCCESS";
	  case DDC_FAILURE:          return "DDC_FAILURE";
	  case DDC_OUT_OF_MEMORY:    return "DDC_OUT_OF_MEMORY";
	  case DDC_FILE_ERROR:       return "DDC_FILE_ERROR";
	  case DDC_INVALID_CALL:     return "DDC_INVALID_CALL";
	  case DDC_USER_ABORT:       return "DDC_USER_ABORT";
	  case DDC_INVALID_FILE:     return "DDC_INVALID_FILE";
     }
     return "Unknown Error";   
   }

   /**
    * Fill the header.
	*/
   public static int FourCC(String ChunkName)
   {
      byte[] p = {0x20,0x20,0x20,0x20};
	  ChunkName.getBytes(0,4,p,0);
	  int ret = (((p[0] << 24)& 0xFF000000) | ((p[1] << 16)&0x00FF0000) | ((p[2] << 8)&0x0000FF00) | (p[3]&0x000000FF));
      return ret;
   }

}
